1
Passer aux graphiques haute performance
AI020Lesson 8
00:00

En informatique graphique, nous faisons la distinction entre Vecteur et Bitmap graphiques. Les graphiques vectoriels (comme SVG) décrivent les images par des formes logiques ; chaque élément est un objet persistant dans le DOM. À l'inverse, les graphiques bitmap (comme HTML5 Canvas) fonctionnent avec grilles de points colorés.

1. Le passage à Canvas

Bien que SVG soit plus facile à styliser via CSS, le navigateur doit suivre chaque nœud. Pour les besoins de haute performance, comme les jeux comportant des milliers d'éléments mobiles, l'API Canvas est supérieure. Elle fournit un unique élément DOM qui encapsule une surface de dessin — essentiellement un « tableau vierge ».

2. Le contexte de dessin

L' <canvas> élément est une "boîte noire" jusqu'à ce que nous initialisions son contexte. Les méthodes de cet objet fournissent l'interface réelle de dessin, en dissociant l'élément d'affichage de la logique de rendu.

var contexte = canvas.getContext("2d");

3. Conscience des espaces de noms

Dans les graphiques basés sur XML comme SVG, l'attribut xmlns="http://www.w3.org/2000/svg" est critique. Il indique au navigateur de passer de l'analyse HTML standard au schéma graphique spécifique, permettant aux balises de forme d'être reconnues comme objets interactifs.

main.py
TERMINALbash — 80x24
> Ready. Click "Run" to execute.
>
", "execution_steps": [ { "output": "Rendering SVG: Cyan circle (updated from red) and blue outlined square." }, { "output": "Rendering Canvas: Solid red rectangle drawn via pixel raster." } ] }; const executionSteps = (courseData && courseData.execution_steps) || []; // ── Machine Translation Data Hydration ── const domCode = document.getElementById('data-course-code'); if (domCode && courseData) { courseData.code = domCode.textContent; } const domExecs = document.querySelectorAll('#data-exec-output .data-exec'); domExecs.forEach(el => { const stepIdx = parseInt(el.getAttribute('data-step'), 10); if (!isNaN(stepIdx) && executionSteps[stepIdx]) { executionSteps[stepIdx].output = el.textContent.trim(); } }); const pageNumber = '1'; let visualMode = 'code'; let studyTimerInterval = null; let studyElapsedTime = 0; let studyLastStartTime = 0; let isStudyTimerRunning = false; let isSimRunning = false; let currentStep = 0; let timer = null; let animFrameId; let startTime = 0; // Global State let masterTimeline = null; let hybridInitialized = false; let totalTimelineSteps = 0; // tracks how many step labels the fallback timeline defines const els = { codeContainer: document.getElementById('code-container'), quizContainer: document.getElementById('quiz-container'), examContainer: document.getElementById('exam-container'), tabCode: document.getElementById('tab-code'), tabSim: document.getElementById('tab-sim'), tabQuiz: document.getElementById('tab-quiz'), tabExam: document.getElementById('tab-exam'), visControls: document.getElementById('vis-controls'), visStatusBar: document.getElementById('vis-status-bar'), code: document.getElementById('code-content'), visContainer: document.querySelector('.vis-container'), tipsBtn: document.getElementById('tips-btn'), drawer: document.getElementById('drawer-overlay'), timerDisplay: document.getElementById('study-timer'), timerVal: document.getElementById('timer-val'), consoleOutput: document.getElementById('console-output'), runBtn: document.getElementById('run-btn') }; // --- HELPERS --- /** * [通用渲染方法] General LaTeX Render Method * 封装 MathJax 渲染逻辑,可被多次调用 * @param {HTMLElement | Array} elements - 可选,指定渲染的元素或元素数组 */ function renderLaTeX(elements) { if (window.MathJax && typeof window.MathJax.typesetPromise === 'function') { // 如果传入了具体元素,只渲染这些元素 if (elements) { //确保 elements 是数组形式 const elArray = Array.isArray(elements) ? elements : [elements]; MathJax.typesetPromise(elArray).catch(err => console.warn('MathJax specific render error:', err)); } else { // 否则渲染全页 MathJax.typesetPromise().catch(err => console.warn('MathJax global render error:', err)); } } } // Syntax Highlighter adapted for Python keywords function renderCode(codeStr) { return codeStr.split('\n').map((line, i) => { const lineNum = i + 1; let htmlLine = ''; const regex = /((?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'))|(#.*)|(\b(?:def|class|if|else|elif|while|for|in|return|import|from|as|try|except|finally|with|lambda|pass|break|continue)\b)|(\b(?:print|len|range|enumerate|zip|map|filter|set|list|dict|int|str|float|sum|max|min|append|pop)\b)|(\b(?:True|False|None|[0-9]+)\b)|(\+|-|\*|\/|=|<|>|!|%|\[|\]|\{|\}|\(|\))/g; let lastIndex = 0; let match; while ((match = regex.exec(line)) !== null) { const textBefore = line.slice(lastIndex, match.index); htmlLine += escapeHtml(textBefore); const [fullMatch, str, com, kw, fn, num, op] = match; if (str) htmlLine += `${escapeHtml(chaîne)}`; sinon si (commentaire) htmlLigne += `${escapeHtml(commentaire)}`; sinon si (motClef) htmlLigne += `${escapeHtml(motClef)}`; sinon si (fonction) htmlLigne += `${escapeHtml(fonction)}`; sinon si (nombre) htmlLigne += `${escapeHtml(nombre)}`; sinon si (opérateur) htmlLigne += `${escapeHtml(opérateur)}`; dernierIndex = regex.lastIndex; } htmlLigne += escapeHtml(ligne.slice(dernierIndex)); return `
${numLigne}
${htmlLigne}
`; }).join(''); } function escapeHtml(texte) { if (!texte) return ''; return texte.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } // Aide pour effacer tous les surlignements // Aide pour effacer tous les surlignements function clearHighlights() { const lignes = document.querySelectorAll('.code-line'); lignes.forEach(l => l.classList.remove('executing')); } // --- LOGIQUE D'INTERACTION --- // NOUVEAU : Simulation avancée avec mise en évidence des lignes window.runSimulatedCode = async function () { if (isSimRunning) return; const terme = els.consoleOutput; if (!terme) return; isSimRunning = true; if (els.runBtn) els.runBtn.disabled = true; try { // État initial du terminal terme.innerHTML = `
${courseData.run_cmd || '> python3 main.py'}
`; const wait = (ms) => new Promise(r => setTimeout(r, ms)); await wait(400); // Afficher toutes les sorties en une seule fois (sans mise en évidence ligne par ligne) for (let i = 0; i < executionSteps.length; i++) { const étape = executionSteps[i]; if (étape.output) { const div = document.createElement('div'); div.className = 'console-line'; div.innerText = étape.output; terme.appendChild(div); } } // Terminer const curseur = document.createElement('div'); curseur.className = 'console-line'; curseur.innerHTML = `> `; terme.appendChild(curseur); terme.scrollTop = terme.scrollHeight; } catch (err) { console.error("Erreur de simulation", err); } finally { isSimRunning = false; if (els.runBtn) els.runBtn.disabled = false; } }; // Fonction de copie du code window.copyCode = function () { if (navigator.clipboard) { navigator.clipboard.writeText(courseData.code).then(() => { const bouton = document.querySelector('.ide-btn[onclick="copyCode()"]'); if (bouton) { const parent = bouton.parentElement; const htmlOriginal = parent.innerHTML; parent.innerHTML = ` Copié !`; lucide.createIcons(); setTimeout(() => { parent.innerHTML = htmlOriginal; lucide.createIcons(); }, 2000); } }); } }; // Activation/Désactivation du défi/examen // Logique de sélection du quiz function toggleFullScreen() { const elem = els.visContainer; if (elem) { if (!document.fullscreenElement) { elem.requestFullscreen().catch(err => { console.error(err); }); } else { document.exitFullscreen(); } } } const fsBtn = document.getElementById('fsBtn'); if (fsBtn) { document.addEventListener('fullscreenchange', () => { const estPleinEcran = !!document.fullscreenElement; fsBtn.innerHTML = estPleinEcran ? '' : ''; if (window.lucide) lucide.createIcons(); }); } // Gestionnaire de réponse du quiz (style référence de InnerPage General) window.checkAnswer = function(carton, préfixe, estCorrect) { const grille = carton.closest('.quiz-options-grid'); if (grille.classList.contains('répondu')) return; grille.classList.add('répondu'); carton.classList.add('sélectionné', estCorrect ? 'correct' : 'incorrect'); const icône = document.createElement('i'); icône.setAttribute('data-lucide', estCorrect ? 'check-circle' : 'x-circle'); icône.style.flexShrink = '0'; carton.appendChild(icône); if (window.lucide) lucide.createIcons(); const boîteCorrecte = document.getElementById(préfixe + '-correct'); const boîteIncorrecte = document.getElementById(préfixe + '-incorrect'); if (estCorrect && boîteCorrecte) { boîteCorrecte.style.display = 'block'; } else if (!estCorrect && boîteIncorrecte) { boîteIncorrecte.style.display = 'block'; } }; // Toggle de solution d'examen (style référence de InnerPage General) window.toggleExamSolution = function(bouton) { const divRéponse = bouton.nextElementSibling; const estVisible = divRéponse.classList.toggle('visible'); bouton.innerHTML = estVisible ? 'Masquer la solution ' : 'Afficher la solution '; if (window.lucide) lucide.createIcons(); if (estVisible) renderLaTeX(divRéponse); }; // --- LOGIQUE DE L'APPLICATION --- // Réinitialisation de la carte du quiz window.resetQuizCard = function(bouton) { const carte = bouton.closest('.quiz-card'); if (!carte) return; const grille = carte.querySelector('.quiz-options-grid'); if (grille) { grille.classList.remove('répondu'); grille.querySelectorAll('.quiz-opt-card').forEach(opt => { opt.classList.remove('sélectionné', 'correct', 'incorrect'); const icône = opt.querySelector('i'); if (icône) icône.remove(); }); } carte.querySelectorAll('.quiz-feedback-box').forEach(fb => { fb.style.display = 'none'; fb.classList.remove('visible'); }); }; function init() { try { if (courseData.filename && document.getElementById('code-filename-display')) { document.getElementById('code-filename-display').textContent = courseData.filename; } loadContent(); } catch (e) { console.error("Échec du chargement du contenu", e); } try { initStudyTimer(); } catch (e) { console.error("Échec de l'initialisation du minuteur", e); } if (courseData.visual && courseData.visual.simStructure && !courseData.visual.simSteps) { courseData.visual.simSteps = []; } if (els.tabCode) els.tabCode.onclick = () => setVisualMode('code'); if (els.tabSim) els.tabSim.onclick = () => setVisualMode('sim'); if (els.tabQuiz) els.tabQuiz.onclick = () => setVisualMode('quiz'); if (els.tabExam) els.tabExam.onclick = () => setVisualMode('exam'); if (document.getElementById('btn-play') && typeof togglePlay === 'function') document.getElementById('btn-play').onclick = togglePlay; if (document.getElementById('btn-next') && typeof step === 'function') document.getElementById('btn-next').onclick = () => step(1); if (document.getElementById('btn-prev') && typeof step === 'function') document.getElementById('btn-prev').onclick = () => step(-1); if (document.getElementById('btn-reset') && typeof resetSim === 'function') document.getElementById('btn-reset').onclick = resetSim; if (els.tipsBtn) els.tipsBtn.onclick = () => els.drawer.classList.add('active'); const closeDrawer = document.getElementById('close-drawer'); if (closeDrawer) closeDrawer.onclick = () => els.drawer.classList.remove('active'); if (els.drawer) els.drawer.onclick = (e) => { if (e.target === els.drawer) els.drawer.classList.remove('active'); }; } function initStudyTimer() { startStudyTimer(); if (els.timerDisplay) els.timerDisplay.onclick = toggleStudyTimer; } function toggleStudyTimer() { if (isStudyTimerRunning) { pauseStudyTimer(); } else { startStudyTimer(); } } function startStudyTimer() { if (isStudyTimerRunning) return; isStudyTimerRunning = true; studyLastStartTime = Date.now(); if (els.timerDisplay) els.timerDisplay.classList.remove('paused'); updateTimerDisplay(); studyTimerInterval = setInterval(updateTimerDisplay, 1000); } function pauseStudyTimer() { if (!isStudyTimerRunning) return; isStudyTimerRunning = false; studyElapsedTime += Date.now() - studyLastStartTime; clearInterval(studyTimerInterval); if (els.timerDisplay) els.timerDisplay.classList.add('paused'); updateTimerDisplay(); } function updateTimerDisplay() { let totalMs = studyElapsedTime; if (isStudyTimerRunning) totalMs += (Date.now() - studyLastStartTime); const totalSecs = Math.floor(totalMs / 1000); const m = Math.floor(totalSecs / 60).toString().padStart(2, '0'); const s = (totalSecs % 60).toString().padStart(2, '0'); if (els.timerVal) els.timerVal.innerText = `${m}:${s}`; } function loadContent() { renderLaTeX(); if (courseData.code && els.code) els.code.innerHTML = renderCode(courseData.code); setVisualMode(visualMode); } function setVisualMode(mode) { visualMode = mode; if (els.tabCode) els.tabCode.classList.toggle('active', mode === 'code'); if (els.tabSim) els.tabSim.classList.toggle('active', mode === 'sim'); if (els.tabQuiz) els.tabQuiz.classList.toggle('active', mode === 'quiz'); if (els.tabExam) els.tabExam.classList.toggle('active', mode === 'exam'); if (els.codeContainer) els.codeContainer.style.display = 'none'; if (els.quizContainer) els.quizContainer.style.display = 'none'; if (els.examContainer) els.examContainer.style.display = 'none'; if (els.canvas) els.canvas.style.display = 'none'; if (els.visControls) els.visControls.style.display = 'none'; if (els.visStatusBar) els.visStatusBar.style.display = 'none'; if (mode === 'code') { if (els.codeContainer) els.codeContainer.style.display = 'flex'; } else if (mode === 'quiz') { if (els.quizContainer) { els.quizContainer.style.display = 'block'; renderLaTeX(els.quizContainer); } } else if (mode === 'exam') { if (els.examContainer) { els.examContainer.style.display = 'block'; renderLaTeX(els.examContainer); } } } init(); window.addEventListener('resize', () => {});